<template>
    <div :class="['custom-editor', theme, { fullscreen, readonly }]">
        <!-- 工具栏 -->
        <div class="editor-toolbar" :class="{ readonly }">
            <div class="toolbar-group">
                <button class="toolbar-btn" :class="{ active: activeStates.paragraph }"
                    @click="exec('formatBlock', '<p>')" :disabled="readonly"><span class="icon">T</span><span
                        class="txt">正文</span></button>
                <button class="toolbar-btn" :class="{ active: activeStates.blockquote }"
                    @click="handleBlockquote" :disabled="readonly"><span class="icon">“</span><span
                        class="txt">引用</span></button>
                <button class="toolbar-btn" @click="clearFormat" :disabled="readonly"><span class="icon">✖</span><span
                        class="txt">清除</span></button>
                <button class="toolbar-btn" @click="insertHr" :disabled="readonly"><span class="icon">―</span><span
                        class="txt">分割线</span></button>
            </div>
            <div class="toolbar-group">
                <button class="toolbar-btn" :class="{ active: activeStates.bold }" @click="exec('bold')"><span
                        class="icon"><b>B</b></span><span class="txt">加粗</span></button>
                <button class="toolbar-btn" :class="{ active: activeStates.underline }" @click="exec('underline')"><span
                        class="icon"><u>U</u></span><span class="txt">下划线</span></button>
                <button class="toolbar-btn" :class="{ active: activeStates.italic }" @click="exec('italic')"><span
                        class="icon"><i>I</i></span><span class="txt">斜体</span></button>
            </div>
            <div class="toolbar-group">
                <button class="toolbar-btn" ref="fontSizeBtn" @click="toggleDropdown('fontSize')"><span
                        class="icon">A</span><span class="txt">字号</span></button>
                <button class="toolbar-btn" ref="fontFamilyBtn" @click="toggleDropdown('fontFamily')"><span
                        class="icon">A</span><span class="txt">字体</span></button>
                <button class="toolbar-btn" ref="lineHeightBtn" @click="toggleDropdown('lineHeight')"><span
                        class="icon">↕</span><span class="txt">行高</span></button>
            </div>
            <!-- 绝对定位下拉菜单 -->
            <div v-if="dropdown === 'fontSize'" class="dropdown-menu" :style="dropdownStyle(fontSizeBtn) as any">
                <div v-for="n in [1, 2, 3, 4, 5, 6, 7]" :key="n" class="dropdown-item"
                    @mousedown.prevent
                    @click="execFontSize(n); closeDropdown()"
                    :class="{ active: currentFontSize === n }">字号{{ n }}</div>
            </div>
            <div v-if="dropdown === 'fontFamily'" class="dropdown-menu" :style="dropdownStyle(fontFamilyBtn) as any">
                <div v-for="f in fontFamilies" :key="f" class="dropdown-item"
                    @mousedown.prevent
                    @click="exec('fontName', f); closeDropdown()">{{ f }}</div>
            </div>
            <div v-if="dropdown === 'lineHeight'" class="dropdown-menu" :style="dropdownStyle(lineHeightBtn) as any">
                <div v-for="h in [1, 1.5, 2, 2.5, 3]" :key="h" class="dropdown-item"
                    @mousedown.prevent
                    @click="setLineHeight(String(h)); closeDropdown()">{{ h }}</div>
            </div>
            <div class="toolbar-group">
                <button class="toolbar-btn" :class="{ active: activeStates.justifyLeft }"
                    @click="exec('justifyLeft')"><span class="icon">≡</span><span class="txt">左对齐</span></button>
                <button class="toolbar-btn" :class="{ active: activeStates.justifyCenter }"
                    @click="exec('justifyCenter')"><span class="icon">≣</span><span class="txt">居中</span></button>
                <button class="toolbar-btn" :class="{ active: activeStates.justifyRight }"
                    @click="exec('justifyRight')"><span class="icon">≡</span><span class="txt">右对齐</span></button>
            </div>
            <div class="toolbar-group">
                <button class="toolbar-btn" @click="openImageDialog"><span class="icon">🖼️</span><span
                        class="txt">图片</span></button>
                <button class="toolbar-btn" @click="handleLink" :disabled="readonly"><span class="icon">🔗</span><span
                        class="txt">链接</span></button>
                <button class="toolbar-btn" v-if="isLinkSelected" @click="removeLink" :disabled="readonly"><span
                        class="icon">❌</span><span class="txt">移除链接</span></button>
                <button class="toolbar-btn" @click="openTableDialog"><span class="icon">▦</span><span
                        class="txt">表格</span></button>
                <button class="toolbar-btn" @click="openCodeDialog"><span class="icon">{ }</span><span
                        class="txt">代码</span></button>
            </div>
            <div class="toolbar-group">
                <button class="toolbar-btn" @click="exec('undo')" :disabled="!canUndo || readonly"><span
                        class="icon">↺</span><span class="txt">撤销</span></button>
                <button class="toolbar-btn" @click="exec('redo')" :disabled="!canRedo || readonly"><span
                        class="icon">↻</span><span class="txt">重做</span></button>
                <button class="toolbar-btn" @click="toggleFullscreen" :disabled="readonly"><span
                        class="icon">⛶</span><span class="txt">全屏</span></button>
            </div>
            <div class="toolbar-group">
                <button class="toolbar-btn" :class="{ active: activeStates.orderedList }" @click="exec('insertOrderedList')"><span class="icon">1.</span><span
                        class="txt">有序</span></button>
                <button class="toolbar-btn" :class="{ active: activeStates.unorderedList }" @click="exec('insertUnorderedList')"><span class="icon">•</span><span
                        class="txt">无序</span></button>
                <button class="toolbar-btn" @click="exec('insertCheckbox')"><span class="icon">☑</span><span
                        class="txt">复选</span></button>
            </div>
            <div class="toolbar-group">
                <button class="toolbar-btn" @click="toggleTheme"><span class="icon">🌓</span><span class="txt">{{ theme
                    === 'dark' ? '亮色' : '暗色' }}</span></button>
            </div>
            <div class="toolbar-group">
                <button class="toolbar-btn" ref="emojiBtn" @click="toggleDropdown('emoji')" :disabled="readonly"><span
                        class="icon">😊</span><span class="txt">表情</span></button>
            </div>
            <div v-if="dropdown === 'emoji'" class="emoji-panel" :style="dropdownStyle(emojiBtn) as any">
                <span v-for="e in emojis" :key="e" class="emoji-item" @mousedown.prevent @click="insertEmoji(e)">{{ e }}</span>
            </div>
            <div class="toolbar-stats">
                <span>字数: {{ charCount }}</span>
                <span>段落: {{ paragraphCount }}</span>
            </div>
        </div>
        <!-- 编辑区 -->
        <div class="editor-content" :contenteditable="!readonly" :class="{ 'dark-content': theme === 'dark', readonly }"
            ref="editorRef" @input="onInput; saveRange()" @mouseup="saveRange()" @keyup="saveRange()" @blur="onBlur"></div>
        <!-- 表格/图片/代码块弹窗、input等保留原有实现 -->
        <div v-if="showTableDialog" class="table-dialog">
            <div>插入表格</div>
            <label>行数: <input type="number" v-model.number="tableRows" min="1" max="10" /></label>
            <label>列数: <input type="number" v-model.number="tableCols" min="1" max="10" /></label>
            <button @click="insertTable">插入</button>
            <button @click="showTableDialog = false">取消</button>
        </div>
        <input v-if="showImageInput" type="file" accept="image/*" style="display:none" ref="imageInputRef"
            @change="onImageChange" />
        <div v-if="showCodeDialog" class="code-dialog">
            <div>插入代码块</div>
            <label>语言:
                <select v-model="codeLang">
                    <option value="javascript">JavaScript</option>
                    <option value="typescript">TypeScript</option>
                    <option value="html">HTML</option>
                    <option value="css">CSS</option>
                    <option value="json">JSON</option>
                    <option value="python">Python</option>
                    <option value="java">Java</option>
                    <option value="c">C</option>
                    <option value="cpp">C++</option>
                    <option value="shell">Shell</option>
                </select>
            </label>
            <textarea v-model="codeContent" rows="6" style="width:300px"></textarea>
            <button @click="insertCodeBlock">插入</button>
            <button @click="showCodeDialog = false">取消</button>
        </div>
    </div>
</template>

<script setup lang="ts">
import { ref, watch, computed, nextTick, onMounted, onBeforeUnmount, reactive } from 'vue';
import hljs from 'highlight.js/lib/core';
import javascript from 'highlight.js/lib/languages/javascript';
import typescript from 'highlight.js/lib/languages/typescript';
import xml from 'highlight.js/lib/languages/xml';
import css from 'highlight.js/lib/languages/css';
import json from 'highlight.js/lib/languages/json';
import python from 'highlight.js/lib/languages/python';
import java from 'highlight.js/lib/languages/java';
import c from 'highlight.js/lib/languages/c';
import cpp from 'highlight.js/lib/languages/cpp';
import shell from 'highlight.js/lib/languages/shell';
import 'highlight.js/styles/github.css';

hljs.registerLanguage('javascript', javascript);
hljs.registerLanguage('typescript', typescript);
hljs.registerLanguage('html', xml);
hljs.registerLanguage('xml', xml);
hljs.registerLanguage('css', css);
hljs.registerLanguage('json', json);
hljs.registerLanguage('python', python);
hljs.registerLanguage('java', java);
hljs.registerLanguage('c', c);
hljs.registerLanguage('cpp', cpp);
hljs.registerLanguage('shell', shell);

const props = defineProps({
    modelValue: { type: String, default: '' },
    readonly: { type: Boolean, default: false },
});
const emit = defineEmits(['update:modelValue', 'change', 'blur']);
const editorRef = ref<HTMLElement | null>(null);
const theme = ref<'light' | 'dark'>('light');
const fullscreen = ref(false);
const showFontSize = ref(false);
const showFontFamily = ref(false);
const showLineHeight = ref(false);
const showImageInput = ref(false);
const imageInputRef = ref<HTMLInputElement | null>(null);
const showTableDialog = ref(false);
const tableRows = ref(3);
const tableCols = ref(3);
const showCodeDialog = ref(false);
const codeContent = ref('');
const codeLang = ref('javascript');
const innerHtml = ref(props.modelValue);
const fontFamilies = ['Arial', '宋体', '微软雅黑', 'serif', 'monospace'];
const dropdown = ref('');
const fontSizeBtn = ref();
const fontFamilyBtn = ref();
const lineHeightBtn = ref();
const emojiBtn = ref();
const emojis = [
    '😀', '😁', '😂', '🤣', '😃', '😄', '😅', '😆', '😉', '😊', '😋', '😎', '😍', '😘', '🥰', '😗', '😙', '😚', '🙂', '🤗', '🤩', '🤔', '🤨', '😐', '😑', '😶', '🙄', '😏', '😣', '😥', '😮', '🤐', '😯', '😪', '😫', '🥱', '😴', '😌', '😛', '😜', '😝', '🤤', '😒', '😓', '😔', '😕', '🙃', '🤑', '😲', '☹️', '🙁', '😖', '😞', '😟', '😤', '😢', '😭', '😦', '😧', '😨', '😩', '🤯', '😬', '😰', '😱', '🥵', '🥶', '😳', '🤪', '😵', '😡', '😠', '🤬', '😷', '🤒', '🤕', '🤢', '🤮', '🥴', '😇', '🥳', '🥺', '🤠', '🤡', '🤥', '🤫', '🤭', '🧐', '🤓', '😈', '👿', '👹', '👺', '💀', '👻', '👽', '🤖', '💩', '😺', '😸', '😹', '😻', '😼', '😽', '🙀', '😿', '😾'
];
const isLinkSelected = computed(() => {
    const sel = window.getSelection();
    if (!sel || !sel.rangeCount) return false;
    let node = sel.anchorNode as HTMLElement | null;
    if (!node) return false;
    if (node.nodeType === 3) node = node.parentElement;
    if (!node) return false;
    return node.closest('a') !== null;
});
const canUndo = ref(false);
const canRedo = ref(false);

const charCount = computed(() => {
    const text = innerHtml.value.replace(/<[^>]+>/g, '').replace(/\s/g, '');
    return text.length;
});
const paragraphCount = computed(() => {
    const html = innerHtml.value;
    return (html.match(/<p[\s>]/g) || []).length || 1;
});

const activeStates = reactive({
  bold: false,
  italic: false,
  underline: false,
  blockquote: false,
  paragraph: false,
  justifyLeft: false,
  justifyCenter: false,
  justifyRight: false,
  orderedList: false,
  unorderedList: false,
});
const fontSizeMap = {
  1: '12px',
  2: '14px',
  3: '16px',
  4: '18px',
  5: '24px',
  6: '32px',
  7: '48px'
};
const currentFontSize = ref(3); // 默认16px

function updateActiveStates() {
  const sel = window.getSelection();
  let node = sel && sel.anchorNode ? sel.anchorNode : null;
  if (node && node.nodeType === 3) node = node.parentElement;
  const el = node as HTMLElement | null;
  activeStates.bold = document.queryCommandState('bold');
  activeStates.italic = document.queryCommandState('italic');
  activeStates.underline = document.queryCommandState('underline');
  activeStates.justifyLeft = document.queryCommandState('justifyLeft');
  activeStates.justifyCenter = document.queryCommandState('justifyCenter');
  activeStates.justifyRight = document.queryCommandState('justifyRight');
  activeStates.orderedList = document.queryCommandState('insertOrderedList');
  activeStates.unorderedList = document.queryCommandState('insertUnorderedList');

  // 优化 blockquote 判断，确保光标在 blockquote 内时高亮
  let inBlockquote = false;
  let cur = el;
  while (cur) {
    if (cur.nodeType === 1 && (cur as HTMLElement).tagName === 'BLOCKQUOTE') {
      inBlockquote = true;
      break;
    }
    cur = (cur as HTMLElement).parentElement;
  }
  activeStates.blockquote = inBlockquote;

  // 段落高亮
  activeStates.paragraph = !!(el && el.closest && el.closest('p')) && !activeStates.blockquote;

  // 获取当前字号，宽松匹配
  let size = 3;
  if (el) {
    let node: HTMLElement | null = el;
    outer: while (node && node !== editorRef.value) {
      let fontSize = node.style.fontSize || window.getComputedStyle(node).fontSize;
      for (const [k, v] of Object.entries(fontSizeMap)) {
        if (Math.abs(parseFloat(fontSize) - parseFloat(v)) < 1) {
          size = Number(k);
          break outer;
        }
      }
      node = node.parentElement;
    }
  }
  currentFontSize.value = size;
}
function updateUndoRedoState() {
    canUndo.value = document.queryCommandEnabled('undo');
    canRedo.value = document.queryCommandEnabled('redo');
}

function onInput() {
    // 只更新v-model，不刷新innerHTML，避免光标跳动
    innerHtml.value = editorRef.value?.innerHTML || '';
    emit('update:modelValue', innerHtml.value);
    emit('change', innerHtml.value);
    updateUndoRedoState();
    updateActiveStates();
}
function onBlur() {
    emit('blur', editorRef.value?.innerHTML || '');
}
function toggleTheme() {
    theme.value = theme.value === 'light' ? 'dark' : 'light';
}
function exec(cmd: string, value?: any) {
    editorRef.value?.focus();
    restoreRange();
    document.execCommand(cmd, false, value);
    updateActiveStates();
    saveRange();
    editorRef.value?.focus();
}
function setLineHeight(val: string) {
    editorRef.value?.focus();
    // 选区包裹span设置行高
    restoreRange();
    document.execCommand('formatBlock', false, 'p');
    const sel = window.getSelection();
    if (sel && sel.rangeCount) {
        const range = sel.getRangeAt(0);
        const el = document.createElement('span');
        el.style.lineHeight = val;
        range.surroundContents(el);
    }
    showLineHeight.value = false;
    onInput();
    editorRef.value?.focus();
}
function toggleFullscreen() {
    fullscreen.value = !fullscreen.value;
}
function openImageDialog() {
    showImageInput.value = true;
    nextTick(() => {
        imageInputRef.value?.click();
    });
}
function onImageChange(e: Event) {
    const files = (e.target as HTMLInputElement).files;
    if (!files || !files[0]) return;
    const file = files[0];
    // 本地预览
    const reader = new FileReader();
    reader.onload = (evt) => {
        const img = document.createElement('img');
        img.src = evt.target?.result as string;
        insertNodeAtCursor(img);
        onInput();
    };
    reader.readAsDataURL(file);
    showImageInput.value = false;
    if (imageInputRef.value) imageInputRef.value.value = '';
}
function insertNodeAtCursor(node: Node) {
    const sel = window.getSelection();
    if (!sel || !sel.rangeCount) return;
    const range = sel.getRangeAt(0);
    range.collapse(false);
    range.insertNode(node);
    // 移动光标到插入节点后
    range.setStartAfter(node);
    range.collapse(true);
    sel.removeAllRanges();
    sel.addRange(range);
}
function openTableDialog() {
    showTableDialog.value = true;
}
function insertTable() {
    restoreRange();
    const rows = tableRows.value;
    const cols = tableCols.value;
    let html = '<table border="1" style="border-collapse:collapse;width:100%">';
    for (let r = 0; r < rows; r++) {
        html += '<tr>';
        for (let c = 0; c < cols; c++) {
            html += '<td style="min-width:40px;height:32px">&nbsp;</td>';
        }
        html += '</tr>';
    }
    html += '</table>';
    const temp = document.createElement('div');
    temp.innerHTML = html;
    insertNodeAtCursor(temp.firstChild!);
    showTableDialog.value = false;
    onInput();
}
function openCodeDialog() {
    showCodeDialog.value = true;
    codeContent.value = '';
    codeLang.value = 'javascript';
}
function insertCodeBlock() {
    restoreRange();
    const pre = document.createElement('pre');
    const code = document.createElement('code');
    code.textContent = codeContent.value;
    code.className = `language-${codeLang.value}`;
    pre.appendChild(code);
    insertNodeAtCursor(pre);
    showCodeDialog.value = false;
    nextTick(() => {
        highlightAllCodeBlocks();
        onInput();
    });
}
function highlightAllCodeBlocks() {
    if (!editorRef.value) return;
    const codes = editorRef.value.querySelectorAll('pre code');
    codes.forEach((block) => {
        hljs.highlightElement(block as HTMLElement);
    });
}
function toggleDropdown(type: string) {
    dropdown.value = dropdown.value === type ? '' : type;
}
function closeDropdown() {
    dropdown.value = '';
}
function dropdownStyle(btnRef: any) {
    if (!btnRef.value) return {};
    const rect = btnRef.value.getBoundingClientRect();
    return {
        position: 'fixed',
        left: rect.left + 'px',
        top: rect.bottom + 4 + 'px',
        zIndex: 20000,
        minWidth: rect.width + 'px',
    };
}
function handleClickOutside(e: MouseEvent) {
    if (!fontSizeBtn.value || !fontFamilyBtn.value || !lineHeightBtn.value) return;
    const targets = [fontSizeBtn.value, fontFamilyBtn.value, lineHeightBtn.value];
    if (dropdown.value && !targets.some(btn => btn.contains(e.target))) {
        closeDropdown();
    }
}
function isActive(cmd: string, value?: string) {
    const sel = window.getSelection();
    if (!sel || !sel.rangeCount) return false;
    let node = sel.anchorNode as HTMLElement | null;
    if (!node) return false;
    if (node.nodeType === 3) node = node.parentElement;
    if (!node) return false;
    if (cmd === 'bold') return document.queryCommandState('bold');
    if (cmd === 'italic') return document.queryCommandState('italic');
    if (cmd === 'underline') return document.queryCommandState('underline');
    if (cmd === 'justifyLeft') return document.queryCommandState('justifyLeft');
    if (cmd === 'justifyCenter') return document.queryCommandState('justifyCenter');
    if (cmd === 'justifyRight') return document.queryCommandState('justifyRight');
    if (cmd === 'formatBlock' && value) {
        let tag = node.closest('p,blockquote');
        if (!tag) return false;
        return tag.tagName === value;
    }
    return false;
}

function clearFormat() {
    // 1. 先清除基础格式
    restoreRange();
    document.execCommand('removeFormat');
    document.execCommand('unlink');
    
    // 2. 获取当前选区
    const selection = window.getSelection();
    if (!selection || !selection.rangeCount) return;
    const range = selection.getRangeAt(0);
    
    // 3. 递归移除结构性标签
    function unwrapStructuralTags(node: Node) {
        if (!node) return;
        
        if (node.nodeType === Node.ELEMENT_NODE) {
            const element = node as HTMLElement;
            const structuralTags = ['BLOCKQUOTE', 'UL', 'OL', 'HR', 'TABLE', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6'];
            
            if (structuralTags.includes(element.tagName)) {
                // 用子节点替换结构性标签
                const parent = element.parentNode;
                if (parent) {
                    while (element.firstChild) {
                        parent.insertBefore(element.firstChild, element);
                    }
                    parent.removeChild(element);
                }
            } else {
                // 递归处理子节点
                const children = Array.from(element.childNodes);
                children.forEach(unwrapStructuralTags);
            }
        }
    }
    
    // 4. 处理选区内的所有节点
    const container = range.commonAncestorContainer;
    if (container.nodeType === Node.TEXT_NODE) {
        // 如果是文本节点，处理其父元素
        const parentNode = container.parentNode;
        if (parentNode) {
            unwrapStructuralTags(parentNode);
        }
    } else {
        unwrapStructuralTags(container);
    }
    
    // 5. 确保光标位置正确
    if (selection.rangeCount) {
        const newRange = selection.getRangeAt(0);
        if (newRange.collapsed) {
            // 如果选区折叠，确保光标在可编辑位置
            let node = newRange.startContainer;
            while (node && node.nodeType !== Node.TEXT_NODE) {
                if (node.nodeType === Node.ELEMENT_NODE) {
                    const element = node as HTMLElement;
                    if (element.tagName === 'DIV' || element.tagName === 'P') {
                        break;
                    }
                }
                node = node.parentNode;
                if (!node) break;
            }
        }
    }
    
    onInput();
}
function insertHr() {
    // 插入分割线时前后加空段落，方便选中和删除
    restoreRange();
    const hr = document.createElement('hr');
    const beforeP = document.createElement('p');
    beforeP.innerHTML = '<br>';
    const afterP = document.createElement('p');
    afterP.innerHTML = '<br>';
    
    // 插入到光标位置
    const sel = window.getSelection();
    if (sel && sel.rangeCount) {
        const range = sel.getRangeAt(0);
        range.insertNode(beforeP);
        range.insertNode(hr);
        range.insertNode(afterP);
        
        // 移动光标到分割线后的段落
        range.setStartAfter(afterP);
        range.collapse(true);
        sel.removeAllRanges();
        sel.addRange(range);
    }
    
    onInput();
}
function handleLink() {
    restoreRange();
    const sel = window.getSelection();
    if (!sel || sel.isCollapsed) {
        alert('请先选中文本再添加链接');
        return;
    }
    const url = window.prompt('输入链接地址');
    if (url) exec('createLink', url);
}
function removeLink() {
    restoreRange();
    exec('unlink');
    onInput();
}

function handleBlockquote() {
    restoreRange();
    const sel = window.getSelection();
    if (!sel || !sel.rangeCount) return;
    const range = sel.getRangeAt(0);
    // 判断是否已在blockquote内
    let node = sel.anchorNode as HTMLElement | null;
    if (node && node.nodeType === 3) node = node.parentElement;
    if (node && node.closest('blockquote')) {
        // 已在blockquote内，移除blockquote
        const bq = node.closest('blockquote');
        if (bq && bq.parentNode) {
            while (bq.firstChild) bq.parentNode.insertBefore(bq.firstChild, bq);
            bq.parentNode.removeChild(bq);
        }
        onInput();
        return;
    }
    // 否则插入blockquote
    const bq = document.createElement('blockquote');
    if (!sel.isCollapsed) {
        bq.appendChild(range.extractContents());
        range.insertNode(bq);
    } else {
        bq.innerHTML = '<br>';
        range.insertNode(bq);
        // 光标移到blockquote内
        sel.removeAllRanges();
        const newRange = document.createRange();
        newRange.selectNodeContents(bq);
        newRange.collapse(true);
        sel.addRange(newRange);
    }
    onInput();
}

function handleKeydown(e: KeyboardEvent) {
    if (e.ctrlKey || e.metaKey) {
        if (e.key === 'b' || e.key === 'B') {
            e.preventDefault(); exec('bold');
        } else if (e.key === 'i' || e.key === 'I') {
            e.preventDefault(); exec('italic');
        } else if (e.key === 'u' || e.key === 'U') {
            e.preventDefault(); exec('underline');
        } else if (e.key === 'z' || e.key === 'Z') {
            e.preventDefault(); exec('undo');
        } else if (e.key === 'y' || e.key === 'Y') {
            e.preventDefault(); exec('redo');
        }
    } else if (e.key === 'Tab') {
        e.preventDefault();
        if (e.shiftKey) {
            exec('outdent');
        } else {
            exec('indent');
        }
    } else if (e.key === 'Backspace' || e.key === 'Delete') {
        // 处理分割线删除
        restoreRange();
        const sel = window.getSelection();
        if (sel && sel.rangeCount) {
            const range = sel.getRangeAt(0);
            let node = range.startContainer;
            
            // 检查光标是否在分割线附近
            if (node.nodeType === Node.TEXT_NODE) {
                const parentNode = node.parentNode;
                if (parentNode) {
                    node = parentNode;
                }
            }
            
            if (node && node.nodeType === Node.ELEMENT_NODE) {
                const element = node as HTMLElement;
                
                // 检查前后是否有分割线
                const prevSibling = element.previousElementSibling;
                const nextSibling = element.nextElementSibling;
                
                if (prevSibling && prevSibling.tagName === 'HR') {
                    // 删除前面的分割线
                    prevSibling.remove();
                    e.preventDefault();
                    onInput();
                    return;
                }
                
                if (nextSibling && nextSibling.tagName === 'HR') {
                    // 删除后面的分割线
                    nextSibling.remove();
                    e.preventDefault();
                    onInput();
                    return;
                }
                
                // 检查当前元素是否是分割线
                if (element.tagName === 'HR') {
                    element.remove();
                    e.preventDefault();
                    onInput();
                    return;
                }
            }
        }
    } else if (e.key === 'Escape') {
        // 关闭所有弹窗
        closeDropdown();
        showTableDialog.value = false;
        showCodeDialog.value = false;
        showImageInput.value = false;
    }
}
function insertTime() {
    restoreRange();
    const now = new Date();
    const str = now.toLocaleString();
    insertNodeAtCursor(document.createTextNode(str));
    onInput();
}
let savedRange: Range | null = null;
function saveRange() {
    const sel = window.getSelection();
    if (sel && sel.rangeCount > 0) {
        savedRange = sel.getRangeAt(0);
    }
}
function restoreRange() {
    if (savedRange) {
        const sel = window.getSelection();
        sel?.removeAllRanges();
        sel?.addRange(savedRange);
    }
}
function execFontSize(n: number) {
  editorRef.value?.focus();
  restoreRange();
  document.execCommand('fontSize', false, String(n));
  // 替换 font 标签为 span 并加 style
  if (editorRef.value) {
    const fonts = editorRef.value.querySelectorAll('font[size]');
    fonts.forEach(font => {
      const sizeAttr = font.getAttribute('size');
      const sizeNum = Number(sizeAttr);
      const span = document.createElement('span');
      span.style.fontSize = fontSizeMap[sizeNum as keyof typeof fontSizeMap] || '16px';
      span.innerHTML = font.innerHTML;
      font.parentNode?.replaceChild(span, font);
    });
  }
  currentFontSize.value = n; // 直接赋值，确保高亮
  updateActiveStates();
  onInput();
  editorRef.value?.focus();
}
function insertEmoji(e: string) {
  editorRef.value?.focus();
  restoreRange();
  if (editorRef.value) {
    editorRef.value.focus();
  }
  insertNodeAtCursor(document.createTextNode(e));
  onInput();
  saveRange();
  closeDropdown();
  editorRef.value?.focus();
}
onMounted(() => {
    window.addEventListener('selectionchange', () => setTimeout(updateActiveStates, 0));
    window.addEventListener('click', handleClickOutside, true);
    if (editorRef.value) {
        editorRef.value.addEventListener('keydown', handleKeydown);
        editorRef.value.addEventListener('click', updateActiveStates);
    }
    highlightAllCodeBlocks();
    updateUndoRedoState();
    updateActiveStates();
});
onBeforeUnmount(() => {
    window.removeEventListener('selectionchange', updateActiveStates);
    window.removeEventListener('click', handleClickOutside, true);
    if (editorRef.value) {
        editorRef.value.removeEventListener('keydown', handleKeydown);
        editorRef.value.removeEventListener('click', updateActiveStates);
    }
});
watch(() => props.modelValue, (val) => {
    // 只在外部v-model变化时同步内容，避免每次输入都刷新导致光标跳动
    if (editorRef.value && editorRef.value.innerHTML !== val) {
        editorRef.value.innerHTML = val;
        innerHtml.value = val;
    }
});
watch(() => props.readonly, (v) => {
    if (v && editorRef.value) editorRef.value.blur();
});
// 弹窗自动聚焦
watch(showTableDialog, v => {
    if (v) nextTick(() => {
        const input = document.querySelector('.table-dialog input');
        if (input) (input as HTMLInputElement).focus();
    });
});
watch(showCodeDialog, v => {
    if (v) nextTick(() => {
        const textarea = document.querySelector('.code-dialog textarea');
        if (textarea) (textarea as HTMLTextAreaElement).focus();
    });
});
// 编辑区事件绑定
function onEditorEvent() { saveRange(); updateActiveStates(); }
</script>

<style scoped>
.custom-editor {
    border: 1px solid #ccc;
    border-radius: 8px;
    background: #fff;
    transition: background 0.3s;
    max-width: 100vw;
}

.custom-editor.dark {
    background: #222;
    color: #eee;
    border-color: #444;
}

.editor-toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding: 8px 4px;
    background: inherit;
    border-bottom: 1px solid #eee;
    overflow-x: auto;
}

.toolbar-group {
    display: flex;
    gap: 4px;
    align-items: center;
    margin-right: 8px;
}

.toolbar-btn {
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 4px 8px;
    border: none;
    background: transparent;
    border-radius: 4px;
    cursor: pointer;
    font-size: 15px;
    transition: background 0.2s;
}

.toolbar-btn:hover,
.toolbar-btn:focus {
    background: #f0f0f0;
}

.custom-editor.dark .toolbar-btn:hover,
.custom-editor.dark .toolbar-btn:focus {
    background: #333;
}

.toolbar-btn .icon {
    font-size: 16px;
    margin-right: 2px;
}

.toolbar-btn .txt {
    font-size: 13px;
}

.toolbar-select {
    margin-left: 4px;
    padding: 2px 6px;
    border-radius: 4px;
    border: 1px solid #ccc;
}

.editor-content {
    min-height: 200px;
    padding: 12px;
    outline: none;
    background: inherit;
    color: inherit;
    font-size: 16px;
    word-break: break-all;
    overflow-x: auto;
}

.dark-content {
    background: #222;
    color: #eee;
}

.dropdown-menu {
    position: fixed;
    background: #fff;
    border: 1px solid #ccc;
    border-radius: 6px;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
    min-width: 60px;
    padding: 4px 0;
    z-index: 20000;
}

.custom-editor.dark .dropdown-menu {
    background: #222;
    color: #eee;
    border-color: #444;
}

.dropdown-item {
    padding: 6px 18px;
    cursor: pointer;
    font-size: 15px;
    transition: background 0.2s;
}

.dropdown-item:hover {
    background: #f0f0f0;
}

.custom-editor.dark .dropdown-item:hover {
    background: #333;
}

.dropdown-item.active {
  background: #e0eaff !important;
  color: #1976d2 !important;
  font-weight: bold;
}

.toolbar-btn.active {
    background: #e0eaff;
    color: #1976d2;
    font-weight: bold;
}

.custom-editor.dark .toolbar-btn.active {
    background: #333a4d;
    color: #90caf9;
}

.fullscreen {
    position: fixed;
    inset: 0;
    z-index: 9999;
    background: #fff;
    border-radius: 0;
    width: 100vw;
    height: 100vh;
    display: flex;
    flex-direction: column;
    animation: fadeInFull 0.3s;
}

.fullscreen .editor-content {
    min-height: 80vh;
}

@keyframes fadeInFull {
    from {
        opacity: 0;
        transform: scale(0.98);
    }

    to {
        opacity: 1;
        transform: scale(1);
    }
}

@media (max-width: 600px) {
    .editor-toolbar {
        flex-wrap: nowrap;
        overflow-x: auto;
        gap: 4px;
        padding: 4px 2px;
    }

    .toolbar-group {
        margin-right: 2px;
        gap: 2px;
    }

    .toolbar-btn {
        font-size: 13px;
        padding: 2px 4px;
    }

    .toolbar-btn .icon {
        font-size: 14px;
    }

    .toolbar-btn .txt {
        font-size: 11px;
    }

    .editor-content {
        font-size: 15px;
        padding: 8px;
    }

    .dropdown-menu {
        min-width: 40px;
        font-size: 13px;
    }

    .dropdown-item {
        padding: 5px 10px;
        font-size: 13px;
    }

    .toolbar-stats {
        font-size: 12px;
        gap: 8px;
    }
}

.toolbar-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    background: #f5f5f5 !important;
    color: #aaa !important;
}

.editor-toolbar.readonly .toolbar-btn {
    pointer-events: none;
    opacity: 0.5;
}

.editor-content.readonly {
    background: #f8f8f8;
    color: #888;
    cursor: not-allowed;
}

.toolbar-stats {
    margin-left: auto;
    display: flex;
    gap: 16px;
    align-items: center;
    font-size: 13px;
    color: #888;
    user-select: none;
}

.emoji-panel {
    position: fixed;
    background: #fff;
    border: 1px solid #ccc;
    border-radius: 8px;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
    padding: 8px 6px 4px 6px;
    z-index: 20000;
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    max-width: 320px;
    min-width: 180px;
}

.custom-editor.dark .emoji-panel {
    background: #222;
    color: #eee;
    border-color: #444;
}

.emoji-item {
    font-size: 22px;
    cursor: pointer;
    padding: 2px 4px;
    border-radius: 4px;
    transition: background 0.15s;
}

.emoji-item:hover {
    background: #f0f0f0;
}

.custom-editor.dark .emoji-item:hover {
    background: #333;
}

@media (max-width: 600px) {
    .emoji-panel {
        min-width: 90vw;
        max-width: 98vw;
        font-size: 20px;
        gap: 2px;
        padding: 6px 2px 2px 2px;
    }

    .emoji-item {
        font-size: 20px;
        padding: 2px 2px;
    }
}

.editor-content blockquote {
    border-left: 4px solid #1976d2 !important;
    background: #f0f4fa !important;
    color: #555 !important;
    margin: 10px 0 !important;
    padding: 10px 18px !important;
    border-radius: 6px !important;
    font-style: italic !important;
    font-size: 15px !important;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
}

.custom-editor.dark .editor-content blockquote {
    background: #23272e;
    color: #b0b0b0;
    border-left-color: #4a90e2;
}

.custom-editor .editor-content blockquote {
    border-left: 4px solid #1976d2 !important;
    background: #f0f4fa !important;
    color: #555 !important;
    margin: 10px 0 !important;
    padding: 10px 18px !important;
    border-radius: 6px !important;
    font-style: italic !important;
    font-size: 15px !important;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
}

.custom-editor .editor-content hr {
    border: none !important;
    height: 2px !important;
    background: linear-gradient(90deg, transparent, #1976d2, transparent) !important;
    margin: 20px 0 !important;
    border-radius: 1px !important;
}

.custom-editor.dark .editor-content hr {
    background: linear-gradient(90deg, transparent, #4a90e2, transparent) !important;
}


</style>
<style>
.editor-content blockquote {
    border-left: 4px solid #1976d2 !important;
    background: #f0f4fa !important;
    color: #555 !important;
    margin: 10px 0 !important;
    padding: 10px 18px !important;
    border-radius: 6px !important;
    font-style: italic !important;
    font-size: 15px !important;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
}

.editor-content hr {
    border: none !important;
    height: 2px !important;
    background: linear-gradient(90deg, transparent, #1976d2, transparent) !important;
    margin: 20px 0 !important;
    border-radius: 1px !important;
}

.dark .editor-content hr {
    background: linear-gradient(90deg, transparent, #4a90e2, transparent) !important;
}
</style>